package scenetracer.core.parser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import scenetracer.core.Camera;
import scenetracer.core.Primitive;
import scenetracer.core.Scene;
import scenetracer.core.SceneParser;
import scenetracer.core.light.PointLight;
import scenetracer.core.parser.Parser.ParserException;
import scenetracer.core.primitive.Box;
import scenetracer.core.primitive.Plane;
import scenetracer.core.primitive.Sphere;
import scenetracer.core.primitive.TriangleMesh;
import scenetracer.core.primitive.Triangle;
import scenetracer.core.shader.ConstantShader;
import scenetracer.core.shader.DiffuseShader;
import scenetracer.core.shader.GlassShader;
import scenetracer.core.shader.MirrorShader;
import scenetracer.core.shader.PhongShader;
import scenetracer.core.shader.Shader;
import scenetracer.core.shader.ShinyShader;
import scenetracer.image.Color;
import scenetracer.math.Matrix4;
import scenetracer.math.Point3;
import scenetracer.math.Vector3;


/**
 * This class provides a static method for loading files in the Sunflow scene
 * file format.
 */
public class SCParser implements SceneParser {
    private Parser p;
    private int numLightSamples;
    private int penumbra;
     
    Map<String, Shader> shaders = new HashMap<String, Shader>();

    public SCParser(int penumbra) {
    	this.penumbra = penumbra;
    }

    public boolean parse(String filename, Scene renderscene) {
    	String localDir = new File(filename).getAbsoluteFile().getParentFile().getAbsolutePath();
        numLightSamples = 1;
        Timer timer = new Timer();
        timer.start();
       System.out.println("Parsing \"" + filename + "\" ...");
        try {
            p = new Parser(filename);
            while (true) {
                String token = p.getNextToken();
                if (token == null)
                    break;
                if (token.equals("image")) {
                    System.out.println("Reading image settings ...");
                    parseImageBlock(renderscene);
                } else if (token.equals("background")) {
                    System.out.println("Reading background ...");
                    parseBackgroundBlock(renderscene);
                } else if (token.equals("accel")) {
                    System.out.println("Reading accelerator type ...");
                    p.getNextToken();
                    System.out.println("Setting accelerator type is not recommended - ignoring");
                } else if (token.equals("filter")) {
                    System.out.println("Reading image filter type ...");
                    parseFilter(renderscene);
                } else if (token.equals("bucket")) {
                    System.out.println("Reading bucket settings ...");
                    p.getNextInt();
                    p.getNextToken();
                } else if (token.equals("photons")) {
                    System.out.println("Reading photon settings ...");
                    parsePhotonBlock(renderscene);
                } else if (token.equals("gi")) {
                    System.out.println("Reading global illumination settings ...");
                    parseGIBlock(renderscene);
                } else if (token.equals("lightserver")) {
                    System.out.println("Reading light server settings ...");
                    parseLightserverBlock(renderscene);
                } else if (token.equals("trace-depths")) {
                    System.out.println("Reading trace depths ...");
                    parseTraceBlock(renderscene);
                } else if (token.equals("camera")) {
                    parseCamera(renderscene);
                } else if (token.equals("shader")) {
                    if (!parseShader(renderscene))
                        return false;
                } else if (token.equals("modifier")) {
                    if (!parseModifier(renderscene))
                        return false;
                } else if (token.equals("override")) {
                	p.getNextToken(); 
                	p.getNextBoolean();
                } else if (token.equals("object")) {
                    parseObjectBlock(renderscene);
                } else if (token.equals("instance")) {
                    parseInstanceBlock(renderscene);
                } else if (token.equals("light")) {
                    parseLightBlock(renderscene);
                } else if (token.equals("texturepath")) {
                    String path = p.getNextToken();
                    if (!new File(path).isAbsolute())
                        path = localDir + File.separator + path;
                } else if (token.equals("includepath")) {
                    String path = p.getNextToken();
                    if (!new File(path).isAbsolute())
                        path = localDir + File.separator + path;
                } else if (token.equals("include")) {
                    String file = localDir + File.separator+ p.getNextToken();
                    System.out.println("Including: \"" + file + "\" ...");
                    renderscene.parse(file, penumbra);
                } else
                    System.out.println("Unrecognized token " + token);
            }
            p.close();
        } catch (ParserException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
            return false;
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
            return false;
        } catch (IOException e) {
            System.out.println(e.getMessage());
            return false;
        }
        timer.end();
        System.out.println("Parsing finished. Took "+ timer);
        return true;
    }

    private void parseImageBlock(Scene renderscene) throws IOException, ParserException {
        p.checkNextToken("{");
        if (p.peekNextToken("resolution")) {
        	renderscene.setWidth(p.getNextInt());
        	renderscene.setHeight(p.getNextInt());
        }
        if (p.peekNextToken("aa")) {
        	renderscene.setAntialiasingMinValue(p.getNextInt());
        	renderscene.setAntialiasingMaxValue(p.getNextInt());
        }
        if (p.peekNextToken("samples")) {
        	renderscene.setSamples(p.getNextInt());
        }
        if (p.peekNextToken("contrast")) {
        	renderscene.setContrast(p.getNextFloat());
        }
        if (p.peekNextToken("filter")) {
        	renderscene.setFilter(p.getNextToken());
        }
        if (p.peekNextToken("jitter")) {
        	renderscene.setJitter(p.getNextBoolean());
        }
        if (p.peekNextToken("show-aa")) {
            System.out.println("Deprecated: show-aa ignored");
            p.getNextBoolean();
        }
        if (p.peekNextToken("output")) {
            System.out.println("Deprecated: output statement ignored");
            p.getNextToken();
        }
        p.checkNextToken("}");
    }

    private void parseBackgroundBlock(Scene renderscene) throws IOException, ParserException {
        p.checkNextToken("{");
        p.checkNextToken("color");
        parseColor();
        p.checkNextToken("}");
    }

    private void parseFilter(Scene renderscene) throws IOException, ParserException {
        System.out.println("Deprecated keyword \"filter\" - set this option in the image block");
        String name = p.getNextToken();

        boolean hasSizeParams = name.equals("box") || name.equals("gaussian") || name.equals("blackman-harris") || name.equals("sinc") || name.equals("triangle");
        if (hasSizeParams) {
            p.getNextFloat();
            p.getNextFloat();
        }
    }

    private void parsePhotonBlock(Scene renderscene) throws ParserException, IOException {

        boolean globalEmit = false;
        p.checkNextToken("{");
        if (p.peekNextToken("emit")) {
            System.out.println("Shared photon emit values are deprectated - specify number of photons to emit per map");
            p.getNextInt();
            globalEmit = true;
        }
        if (p.peekNextToken("global")) {
            System.out.println("Global photon map setting belonds inside the gi block - ignoring");
            if (!globalEmit)
                p.getNextInt();
            p.getNextToken();
            p.getNextInt();
            p.getNextFloat();
        }
        p.checkNextToken("caustics");
        if (!globalEmit)
            p.getNextInt();
        p.getNextToken();
        p.getNextInt();
        p.getNextFloat();
        p.checkNextToken("}");
    }

    private void parseGIBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("type");
        if (p.peekNextToken("irr-cache")) {
            p.checkNextToken("samples");
            p.getNextInt();
            p.checkNextToken("tolerance");
            p.getNextFloat();
            p.checkNextToken("spacing");
            p.getNextFloat();
            p.getNextFloat();
            if (p.peekNextToken("global")) {
            	p.getNextInt();
            	p.getNextToken();
            	p.getNextInt();
            	p.getNextFloat();
            }
        } else if (p.peekNextToken("path")) {
            p.checkNextToken("samples");
            p.getNextInt();
            if (p.peekNextToken("bounces")) {
                System.out.println("Deprecated setting: bounces - use diffuse trace depth instead");
                p.getNextInt();
            }
        } else if (p.peekNextToken("fake")) {
            p.checkNextToken("up");
            parseVector();
            p.checkNextToken("sky");
            parseColor();
            p.checkNextToken("ground");
            parseColor();
        } else if (p.peekNextToken("igi")) {
            p.checkNextToken("samples");
            p.getNextInt();
            p.checkNextToken("sets");
            p.getNextInt();
            if (!p.peekNextToken("b"))
                p.checkNextToken("c");
            p.getNextFloat();
            p.checkNextToken("bias-samples");
            p.getNextInt();
        } else if (p.peekNextToken("ambocc")) {
            p.checkNextToken("bright");
            parseColor();
            p.checkNextToken("dark");
            parseColor();
            p.checkNextToken("samples");
            p.getNextInt();
            if (p.peekNextToken("maxdist")) {
            	p.getNextFloat();
            }
        } else if (p.peekNextToken("none") || p.peekNextToken("null")) {
        } else
            System.out.println("Unrecognized gi engine type \"" + p.getNextToken() + "\" - ignoring");
        p.checkNextToken("}");
    }

    private void parseLightserverBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        if (p.peekNextToken("shadows")) {
            System.out.println("Deprecated: shadows setting ignored");
            p.getNextBoolean();
        }
        if (p.peekNextToken("direct-samples")) {
            System.out.println("Deprecated: use samples keyword in area light definitions");
            numLightSamples = p.getNextInt();
        }
        if (p.peekNextToken("glossy-samples")) {
            System.out.println("Deprecated: use samples keyword in glossy shader definitions");
            p.getNextInt();
        }
        if (p.peekNextToken("max-depth")) {
            System.out.println("Deprecated: max-depth setting - use trace-depths block instead");
            p.getNextInt();
        }
        if (p.peekNextToken("global")) {
            System.out.println("Deprecated: global settings ignored - use photons block instead");
            p.getNextBoolean();
            p.getNextInt();
            p.getNextInt();
            p.getNextInt();
            p.getNextFloat();
        }
        if (p.peekNextToken("caustics")) {
            System.out.println("Deprecated: caustics settings ignored - use photons block instead");
            p.getNextBoolean();
            p.getNextInt();
            p.getNextFloat();
            p.getNextInt();
            p.getNextFloat();
        }
        if (p.peekNextToken("irr-cache")) {
            System.out.println("Deprecated: irradiance cache settings ignored - use gi block instead");
            p.getNextInt();
            p.getNextFloat();
            p.getNextFloat();
            p.getNextFloat();
        }
        p.checkNextToken("}");
    }

    private void parseTraceBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        if (p.peekNextToken("diff"))
        	p.getNextInt();
        if (p.peekNextToken("refl"))
        	p.getNextInt();
        if (p.peekNextToken("refr"))
        	p.getNextInt();
        p.checkNextToken("}");
    }

    private void parseCamera(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("type");
        Camera cam = new Camera();
        String type = p.getNextToken();
        System.out.println("Reading " + type + "camera ...");

        parseCameraTransform(renderscene, cam);

        if (type.equals("pinhole")) {
            p.checkNextToken("fov");
            cam.setFov(p.getNextFloat());
            p.checkNextToken("aspect");
            cam.setAspect(p.getNextFloat());

            renderscene.setCamera(cam);
            
        } else if (type.equals("thinlens")) {
            p.checkNextToken("fov");
            p.getNextFloat();

            p.checkNextToken("aspect");
            p.getNextFloat();

            p.checkNextToken("fdist");
            p.getNextFloat();

            p.checkNextToken("lensr");
            p.getNextFloat();

            if (p.peekNextToken("sides"))
            	p.getNextInt();

            if (p.peekNextToken("rotation")) {
            	p.getNextFloat();

            }
        } else if (type.equals("spherical")) {

        } else if (type.equals("fisheye")) {

        } else {
            System.out.println("Unrecognized camera type: " + p.getNextToken());
            p.checkNextToken("}");
            return;
        }
        p.checkNextToken("}");
    }

    private void parseCameraTransform(Scene renderscene, Camera camera) throws ParserException, IOException {
        if (p.peekNextToken("steps")) {
            int n = p.getNextInt();
            for (int i = 0; i < n; i++)
                parseCameraMatrix(i, renderscene, camera);
        } else
            parseCameraMatrix(-1, renderscene, camera);
    }

    private void parseCameraMatrix(int index, Scene renderscene, Camera camera) throws IOException, ParserException {
        if (p.peekNextToken("transform")) {
        	parseMatrix();
        } else {
            if (index >= 0)
                p.checkNextToken("{");
            p.checkNextToken("eye");
            camera.setEye(parsePoint());
            p.checkNextToken("target");
            camera.setTarget(parsePoint());
            p.checkNextToken("up");
            camera.setUp(parseVector());
            if (index >= 0)
                p.checkNextToken("}");
        }
    }

    private boolean parseShader(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");       
        p.checkNextToken("name");
        String name = p.getNextToken();
        System.out.println("Reading shader: " + name + " ...");
        
        p.checkNextToken("type");
        if (p.peekNextToken("diffuse")) {
        	DiffuseShader diffShader = new DiffuseShader(name, penumbra);
            if (p.peekNextToken("diff")) {
            	
            	diffShader.setDiffColor(parseColor());
            	shaders.put(name, diffShader);
            	
            } else if (p.peekNextToken("texture")) {
            	
            	diffShader.setTexture(p.getNextToken());
            	shaders.put(name, diffShader);

            } else
                System.out.println("Unrecognized option in diffuse shader block: " + p.getNextToken());
        } else if (p.peekNextToken("phong")) {
        	PhongShader phongShader = new PhongShader(name, penumbra);
        	String tex = null;
            if (p.peekNextToken("texture")) {
            	phongShader.setTex(p.getNextToken());
            }
            else {
                p.checkNextToken("diff");
                phongShader.setDColor(parseColor());
            }
            p.checkNextToken("spec");
            phongShader.setSColor(parseColor());            
            phongShader.setExp(p.getNextFloat());
            if (p.peekNextToken("samples")) {
            	phongShader.setSamples(p.getNextInt());
            }
            if (tex != null) {
            }
            else {
            }            
            shaders.put(name, phongShader);
        } else if (p.peekNextToken("amb-occ") || p.peekNextToken("amb-occ2")) {
            String tex = null;
            if (p.peekNextToken("diff") || p.peekNextToken("bright")) {
            	parseColor();
            }
            else if (p.peekNextToken("texture")) {
            	p.getNextToken();
            }
            if (p.peekNextToken("dark")) {
            	parseColor();
                p.checkNextToken("samples");
                p.getNextInt();
                p.checkNextToken("dist");
                p.getNextFloat();
            }
            if (tex == null) {
            }
            else {
            }
        } else if (p.peekNextToken("mirror")) {     	
        	p.checkNextToken("refl");
            MirrorShader mirrorShader = new MirrorShader(name);
            mirrorShader.setRefl(parseColor());
            shaders.put(name, mirrorShader);

        } else if (p.peekNextToken("glass")) {
            p.checkNextToken("eta");
            GlassShader glassShader = new GlassShader(name);
            
            glassShader.setEta(p.getNextFloat());

            p.checkNextToken("color");
            glassShader.setColor(parseColor());

            if (p.peekNextToken("absorbtion.distance"))
            	glassShader.setAbsorbtionDistance(p.getNextFloat());
            if (p.peekNextToken("absorbtion.color")) {
            	glassShader.setAbsorbtionColor(parseColor());
            }

            shaders.put(name, glassShader);
            
        } else if (p.peekNextToken("shiny")) {
        	ShinyShader shinyShader = new ShinyShader(name, penumbra);
            String tex = null;
            if (p.peekNextToken("texture")) {
            	p.getNextToken();
            }
            else {
                p.checkNextToken("diff");
                shinyShader.setDColor(parseColor());
            }
            p.checkNextToken("refl");
            shinyShader.setReflInd(p.getNextFloat());
            shaders.put(name, shinyShader);
 
        } else if (p.peekNextToken("ward")) {
            String tex = null;
            if (p.peekNextToken("texture")) {
            	p.getNextToken();
            }
            else {
                p.checkNextToken("diff");
                parseColor();
            }
            p.checkNextToken("spec");
            parseColor();
            p.checkNextToken("rough");
            p.getNextFloat();
            p.getNextFloat();
            if (p.peekNextToken("samples")) {
            	p.getNextInt();
            }
            if (tex != null) {
            }
            else {
            }
        } else if (p.peekNextToken("view-caustics")) {
        } else if (p.peekNextToken("view-irradiance")) {
        } else if (p.peekNextToken("view-global")) {
        } else if (p.peekNextToken("constant")) {
            p.peekNextToken("color");
            ConstantShader constantShader = new ConstantShader(name, parseColor());
            shaders.put(name, constantShader);
        } else if (p.peekNextToken("janino")) {
            try {
            } finally {
            	
            }
        } else if (p.peekNextToken("id")) {
        } else if (p.peekNextToken("uber")) {
            if (p.peekNextToken("diff"))
            	parseColor();
            if (p.peekNextToken("diff.texture"))
            	p.getNextToken();
            if (p.peekNextToken("diff.blend"))
            	p.getNextFloat();
            if (p.peekNextToken("refl") || p.peekNextToken("spec"))
            	parseColor();
            if (p.peekNextToken("texture")) {
                System.out.println("Deprecated uber shader parameter \"texture\" - please use \"diffuse.texture\" and \"diffuse.blend\" instead");
                p.getNextToken();
                p.getNextFloat();
            }
            if (p.peekNextToken("spec.texture"))
            	p.getNextToken();
            if (p.peekNextToken("spec.blend"))
            	p.getNextFloat();
            if (p.peekNextToken("glossy"))
            	p.getNextFloat();
            if (p.peekNextToken("samples")) {
            	p.getNextInt();
            }
        } else
            System.out.println("Unrecognized shader type: " + p.getNextToken());
        p.checkNextToken("}");
        
        renderscene.setShaders(shaders);
        
        return true;
    }

    private boolean parseModifier(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("name");
        String name = p.getNextToken();
        System.out.println("Reading shader: " + name + " ...");
        p.checkNextToken("type");
        if (p.peekNextToken("bump")) {
            p.checkNextToken("texture");
            p.getNextToken();
            p.checkNextToken("scale");
            p.getNextFloat();
        } else if (p.peekNextToken("normalmap")) {
            p.checkNextToken("texture");
            p.getNextToken();
        } else {
            System.out.println("Unrecognized modifier type: " + p.getNextToken());
        }
        p.checkNextToken("}");
        return true;
    }

    private void parseObjectBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        
        ArrayList<String> shadersArr = new ArrayList<String>();
        
        boolean noInstance = false;
        Matrix4 transform = null;
        String name = null;
        String[] modifiers = null;
        if (p.peekNextToken("noinstance")) {
            noInstance = true;
        } else {
            if (p.peekNextToken("shaders")) {
                int n = p.getNextInt();
                for (int i = 0; i < n; i++)
                    shadersArr.add(p.getNextToken());
            } else {
                p.checkNextToken("shader");
                shadersArr.add(p.getNextToken());
            }
            if (p.peekNextToken("modifiers")) {
                int n = p.getNextInt();
                modifiers = new String[n];
                for (int i = 0; i < n; i++)
                    modifiers[i] = p.getNextToken();
            } else if (p.peekNextToken("modifier"))
                modifiers = new String[] { p.getNextToken() };
            if (p.peekNextToken("transform")) {
                transform = parseMatrix();
            }
        }
        if (p.peekNextToken("accel")) {
        	p.getNextToken();
        }
        p.checkNextToken("type");
        String type = p.getNextToken();
        if (p.peekNextToken("name"))
            name = p.getNextToken();
        else {
        }
        if (type.equals("mesh")) {
        	
			LinkedList<Triangle> triangleList = new LinkedList<Triangle>();
			int numVertices = p.getNextInt();
			int numTriangles = p.getNextInt();
			int a, b, c;
			float[] points = new float[numVertices * 3];
			float[] normals = new float[numVertices * 3];
			float[] uvs = new float[numVertices * 2];

			// use this arrays to work with bounding volumes
			float[] minBound = { 0, 0, 0 };
			float[] maxBound = { 0, 0, 0 };
			int indexBound = 0;
			Point3 min = new Point3();
			Point3 max = new Point3();
			

			for (int i = 0; i < numVertices; i++)
			{
				p.checkNextToken("v");
				points[3 * i + 0] = p.getNextFloat();
				points[3 * i + 1] = p.getNextFloat();
				points[3 * i + 2] = p.getNextFloat();

				for (int j = 0; j < 3; j++)
				{
					indexBound = 3 * i + j;

					if (points[indexBound] < minBound[j])
					{
						minBound[j] = points[indexBound];
					}

					if (points[indexBound] > maxBound[j])
					{
						maxBound[j] = points[indexBound];
					}
				}
				
				min.x = minBound[0];
				min.y = minBound[1];
				min.z = minBound[2];
				
				max.x = maxBound[0];
				max.y = maxBound[1];
				max.z = maxBound[2];

				// TODO agregar normales y uvs
				normals[3 * i + 0] = p.getNextFloat();
				normals[3 * i + 1] = p.getNextFloat();
				normals[3 * i + 2] = p.getNextFloat();
				uvs[2 * i + 0] = p.getNextFloat();
				uvs[2 * i + 1] = p.getNextFloat();
			}

			
			Point3 x, y, z, nx, ny, nz;
			float [] u = new float[3];
			float [] v = new float[3];
			for (int i = 0; i < numTriangles; i++)
			{
				p.checkNextToken("t");

				a = p.getNextInt();
				b = p.getNextInt();
				c = p.getNextInt();


				x = new Point3(points[3 * a + 0], points[3 * a + 1],
						points[3 * a + 2]);
				y = new Point3(points[3 * b + 0], points[3 * b + 1],
						points[3 * b + 2]);
				z = new Point3(points[3 * c + 0], points[3 * c + 1],
						points[3 * c + 2]);
				nx = new Point3(normals[3 * a + 0], normals[3 * a + 1],
						normals[3 * a + 2]);
				ny = new Point3(normals[3 * b + 0], normals[3 * b + 1],
						normals[3 * b + 2]);
				nz = new Point3(normals[3 * c + 0], normals[3 * c + 1],
						normals[3 * c + 2]);

				u[0] = uvs[2 * a];
				u[1] = uvs[2 * b];
				u[2] = uvs[2 * c];
				v[0] = uvs[2 * a + 1];
				v[1] = uvs[2 * b + 1];
				v[2] = uvs[2 * c + 1];
				
				//triangleList.add(new Triangle(x, y, z));
				triangleList.add(new Triangle(x, y, z, nx, ny, nz, u, v));

			}
			
			
			TriangleMesh renderableObject;
			// Solo agregamos el objeto si tiene shader asociado
			//if( shaderForObject != null ) {
				renderableObject = new TriangleMesh(triangleList, transform, min,max);
				renderableObject.setName(name);
				ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
				if(shaders2 != null)
					renderableObject.setShaders(shaders2);
				
				renderscene.addPrimitive(renderableObject);				
			//}	
        	
        	
        	/*
        	System.out.println("Deprecated object type: mesh");
            System.out.println("Reading mesh: " + name + " ...");
            int numVertices = p.getNextInt();
            int numTriangles = p.getNextInt();
            
            Point3[] points = new Point3[numVertices];
            ArrayList<Triangle> triangles = new ArrayList<Triangle>(numTriangles);

            float[] normals = new float[numVertices * 3];
            float[] uvs = new float[numVertices * 2];
            for (int i = 0; i < numVertices; i++) {
                p.checkNextToken("v");
                points[i] = new Point3(p.getNextFloat(), p.getNextFloat(), p.getNextFloat());
                normals[3 * i + 0] = p.getNextFloat();
                normals[3 * i + 1] = p.getNextFloat();
                normals[3 * i + 2] = p.getNextFloat();
                uvs[2 * i + 0] = p.getNextFloat();
                uvs[2 * i + 1] = p.getNextFloat();
            }

            TriangleMesh mesh = new TriangleMesh();
            mesh.setName(name);
            
            ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
            if(shaders2 != null)
            	mesh.setShaders(shaders2);
            
            for (int i = 0; i < numTriangles; i++) {
                p.checkNextToken("t");
                triangles.add(new Triangle(points[p.getNextInt()], points[p.getNextInt()], points[p.getNextInt()], mesh));
            }
            mesh.setTriangles(triangles);
            if(shaders2 != null)
        		renderscene.addPrimitives(triangles);
          //renderscene.addPrimitive(mesh);
            */
            
        } else if (type.equals("flat-mesh")) {

            System.out.println("Deprecated object type: flat-mesh");
            System.out.println("Reading flat mesh: " + name + " ...");
            int numVertices = p.getNextInt();
            int numTriangles = p.getNextInt();
            
            Point3[] points = new Point3[numVertices];
//            List<Triangle_aux> triangles = new ArrayList<Triangle_aux>(numTriangles);
            
            float[] uvs = new float[numVertices * 2];
            for (int i = 0; i < numVertices; i++) {
                p.checkNextToken("v");
                points[i] = new Point3(p.getNextFloat(), p.getNextFloat(), p.getNextFloat());
                p.getNextFloat();
                p.getNextFloat();
                p.getNextFloat();
                uvs[2 * i + 0] = p.getNextFloat();
                uvs[2 * i + 1] = p.getNextFloat();
            }

//            TriangleMesh_aux mesh = new TriangleMesh_aux();
            
            
            
            for (int i = 0; i < numTriangles; i++) {
                p.checkNextToken("t");
                p.getNextInt();
                p.getNextInt();
                p.getNextInt();
            }
            
            
        } else if (type.equals("sphere")) {
            System.out.println("Reading sphere ...");
            Sphere sphere = new Sphere();
            if (transform == null && !noInstance) {
                p.checkNextToken("c");
                sphere.setC(parsePoint());
                p.checkNextToken("r");
                sphere.setR(p.getNextFloat());
                
                if (modifiers != null) {
                }
                noInstance = true;
            }
            else
            {
            	sphere.setTransform(transform);
            }
            
            sphere.setName(name);
            ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
            if(shaders2 != null){
            	sphere.setShaders(shaders2);
            	renderscene.addPrimitive(sphere);
            }
            
        } else if (type.equals("box")) {
        	System.out.println("Reading box ...");
        	Box box = new Box();
        	
        	if(transform != null){
        		box.setTransform(transform);
        	}
        	ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
            if(shaders2 != null){
            	box.setName(name);
            	box.setShaders(shaders2);
            }
            if(box.getShaders() != null)
            	renderscene.addPrimitive(box);
        } else if (type.equals("banchoff")) {
            System.out.println("Reading banchoff ...");
        } else if (type.equals("torus")) {
            System.out.println("Reading torus ...");
            p.checkNextToken("r");
            p.getNextFloat();
            p.getNextFloat();
        } else if (type.equals("plane")) {
            System.out.println("Reading plane ...");
            p.checkNextToken("p");
            Point3 center = parsePoint();
            Plane plane = null;
            if (p.peekNextToken("n")) {            	
            	Vector3 normal = parseVector();
            	plane = new Plane(center, normal);
            } else {
                p.checkNextToken("p");
                Point3 point1 = parsePoint();
                p.checkNextToken("p");
                Point3 point2 = parsePoint();
                plane = new Plane(center, point1, point2);
            }
            
            ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
            if(shaders2 != null){
	            plane.setName(name);
	            plane.setShaders(shaders2);
            	renderscene.addPrimitive(plane);
            }

        } else if (type.equals("cornellbox")) {
            System.out.println("Reading cornell box ...");
            if (transform != null)
                System.out.println("Instancing is not supported on cornell box -- ignoring transform");
            p.checkNextToken("corner0");
            parsePoint();
            p.checkNextToken("corner1");
            parsePoint();
            p.checkNextToken("left");
            parseColor();
            p.checkNextToken("right");
            parseColor();
            p.checkNextToken("top");
            parseColor();
            p.checkNextToken("bottom");
            parseColor();
            p.checkNextToken("back");
            parseColor();
            p.checkNextToken("emit");
            parseColor();
            if (p.peekNextToken("samples")) {
            	p.getNextInt();
            }
            noInstance = true; 
        } else if (type.equals("generic-mesh")) {
        	ArrayList<Shader> shaderForObject=null; 
        	if( shaders != null ) {
        		shaderForObject= getShaderList(shadersArr, name);
			}
			// parse vertices
			p.checkNextToken("points");
			int np = p.getNextInt();
			float[] points = parseFloatArray(np * 3);
			// parse triangle indices
			p.checkNextToken("triangles");
			int nt = p.getNextInt();
			int[] triArray = parseIntArray(nt * 3);
			// parse normals
			int a, b, c;
			LinkedList<Triangle> triangleList = new LinkedList<Triangle>();
			
			float[] normalPoints = null;
			p.checkNextToken("normals");
			if (p.peekNextToken("vertex"))
				normalPoints = parseFloatArray(np * 3);
			else if (p.peekNextToken("facevarying"))
				parseFloatArray(nt * 9);
			else
				p.checkNextToken("none");

			// parse texture coordinates
			float[] uvs = null;
			p.checkNextToken("uvs");
			if (p.peekNextToken("vertex"))
				uvs = parseFloatArray(np * 2);
			else if (p.peekNextToken("facevarying"))
				parseFloatArray(nt * 6);
			else
				p.checkNextToken("none");
			if (p.peekNextToken("face_shaders"))
				parseIntArray(nt);
			
			// use this arrays to work with bounding volumes
			float[] minBound = { 0, 0, 0 };
			float[] maxBound = { 0, 0, 0 };
			int indexBound = 0;
			
			Point3 min = new Point3();
			Point3 max = new Point3();
			
			
			for(int i = 0; i < np; i++)
			{
				for (int j = 0; j < 3; j++)
				{
					indexBound = 3 * i + j;
					
					if (points[indexBound] < minBound[j])
					{
						minBound[j] = points[indexBound];
					}
					
					if (points[indexBound] > maxBound[j])
					{
						maxBound[j] = points[indexBound];
					}
				}
			}
			min.x = minBound[0];
			min.y = minBound[1];
			min.z = minBound[2];
			
			max.x = maxBound[0];
			max.y = maxBound[1];
			max.z = maxBound[2];
			
			Point3 x, y, z, nx, ny, nz;
			float [] u = new float[3];
			float [] v = new float[3];
			for (int i = 0; i < nt; i++)
			{
				//              
				a = triArray[i * 3 + 0];
				b = triArray[i * 3 + 1];
				c = triArray[i * 3 + 2];

				x = new Point3(points[3*a + 0], points[3*a + 1], points[3*a + 2]);
				y = new Point3(points[3*b + 0], points[3*b + 1], points[3*b + 2]);
				z = new Point3(points[3*c + 0], points[3*c + 1], points[3*c + 2]);

				if (normalPoints == null && uvs == null) {
					triangleList.add(new Triangle(x, y, z));
				} else if (normalPoints != null && uvs == null) {
					nx = new Point3(normalPoints[3*a + 0], normalPoints[3*a + 1], normalPoints[3*a + 2]);
					ny = new Point3(normalPoints[3*b + 0], normalPoints[3*b + 1], normalPoints[3*b + 2]);
					nz = new Point3(normalPoints[3*c + 0], normalPoints[3*c + 1], normalPoints[3*c + 2]);
					triangleList.add(new Triangle(x, y, z, nx, ny, nz));
				} else if (normalPoints == null && uvs != null) {
					u[0] = uvs[2 * a];
					u[1] = uvs[2 * b];
					u[2] = uvs[2 * c];
					v[0] = uvs[2 * a + 1];
					v[1] = uvs[2 * b + 1];
					v[2] = uvs[2 * c + 1];
					triangleList.add(new Triangle(x, y, z, u, v));
				} else {
					nx = new Point3(normalPoints[3*a + 0], normalPoints[3*a + 1], normalPoints[3*a + 2]);
					ny = new Point3(normalPoints[3*b + 0], normalPoints[3*b + 1], normalPoints[3*b + 2]);
					nz = new Point3(normalPoints[3*c + 0], normalPoints[3*c + 1], normalPoints[3*c + 2]);
					u[0] = uvs[2 * a];
					u[1] = uvs[2 * b];
					u[2] = uvs[2 * c];
					v[0] = uvs[2 * a + 1];
					v[1] = uvs[2 * b + 1];
					v[2] = uvs[2 * c + 1];
					triangleList.add(new Triangle(x, y, z, nx, ny, nz, u, v));
				}
			}
			
			// Solo agregamos el objeto si tiene shader asociado
			Primitive renderableObject;
			if( shaderForObject != null ) {
				renderableObject = new TriangleMesh(triangleList, transform,min,max);
				renderableObject.setName(name);
				renderableObject.setShaders(shaderForObject);
				renderscene.addPrimitive(renderableObject);
			}
        	
        	
        	
        	/*
            System.out.println("Reading generic mesh: " + name + " ... "); 
            
            p.checkNextToken("points");
            int np = p.getNextInt();
           	Point3[] points = getMeshPoints(np, parseFloatArray(np * 3));

            p.checkNextToken("triangles");
            int nt = p.getNextInt();
            
            
            TriangleMesh mesh = new TriangleMesh();
            ArrayList<Shader> shaders2 = getShaderList(shadersArr, name);
            if(shaders2 != null){
	            mesh.setName(name);
	            mesh.setShaders(shaders2);
	            //mesh.setTriangles(getMeshPrimitives(nt, points, parseIntArray(nt * 3), mesh));
	            renderscene.addPrimitives(getMeshPrimitives(nt, points, parseIntArray(nt * 3), mesh));
	            //System.out.println(meshTriangles.length);
	            //renderscene.addPrimitive(mesh);
            }
            
            p.checkNextToken("normals");
            if (p.peekNextToken("vertex") || p.peekNextToken("facevarying")) {
            	parseFloatArray(np * 3);
            }
            
            else {
                p.checkNextToken("none");
            }
            p.checkNextToken("uvs");
            if (p.peekNextToken("vertex")) {
            	parseFloatArray(np * 2);
            }
            else if (p.peekNextToken("facevarying")) {
            	parseFloatArray(nt * 6);
            }
            else
                p.checkNextToken("none");
            if (p.peekNextToken("face_shaders")) {
            	parseIntArray(nt);
            }
            */
            
        } else if (type.equals("hair")) {
            System.out.println("Reading hair curves: " + name + " ... ");
            p.checkNextToken("segments");
            p.getNextInt();
            p.checkNextToken("width");
            p.getNextFloat();
            p.checkNextToken("points");
            parseFloatArray(p.getNextInt());
        } else if (type.equals("janino-tesselatable")) {
            System.out.println("Reading procedural primitive: " + name + " ... ");
            p.getNextCodeBlock();
            try {
            } finally {
            	
            }
        } else if (type.equals("teapot")) {
            System.out.println("Reading teapot: " + name + " ... ");
            boolean hasTesselationArguments = false;
            if (p.peekNextToken("subdivs")) {
            	p.getNextInt();
                hasTesselationArguments = true;
            }
            if (p.peekNextToken("smooth")) {
            	p.getNextBoolean();
                hasTesselationArguments = true;
            }
            if (hasTesselationArguments) {
            }
            else {
            }
        } else if (type.equals("gumbo")) {
            System.out.println("Reading gumbo: " + name + " ... ");
            boolean hasTesselationArguments = false;
            if (p.peekNextToken("subdivs")) {
            	p.getNextInt();
                hasTesselationArguments = true;
            }
            if (p.peekNextToken("smooth")) {
            	p.getNextBoolean();
                hasTesselationArguments = true;
            }
            if (hasTesselationArguments) {
            }
            else {
            }
        } else if (type.equals("julia")) {
            System.out.println("Reading julia fractal: " + name + " ... ");
            if (p.peekNextToken("q")) {
            	p.getNextFloat();
            	p.getNextFloat();
            	p.getNextFloat();
            	p.getNextFloat();
            }
            if (p.peekNextToken("iterations"))
            	p.getNextInt();
            if (p.peekNextToken("epsilon")) {
            	p.getNextFloat();
            }
        } else if (type.equals("particles") || type.equals("dlasurface")) {
            if (type.equals("dlasurface"))
                System.out.println("Deprecated object type: \"dlasurface\" - please use \"particles\" instead");
            p.checkNextToken("filename");
            String filename = p.getNextToken();
            boolean littleEndian = false;
            if (p.peekNextToken("little_endian"))
                littleEndian = true;
            System.out.println("Loading particle file: " + filename);
            File file = new File(filename);
            FileInputStream stream = new FileInputStream(filename);
            MappedByteBuffer map = stream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
            if (littleEndian)
                map.order(ByteOrder.LITTLE_ENDIAN);
            FloatBuffer buffer = map.asFloatBuffer();
            float[] data = new float[buffer.capacity()];
            for (int i = 0; i < data.length; i++)
                data[i] = buffer.get(i);
            stream.close();
            if (p.peekNextToken("num")) {
            	p.getNextInt();
            }
            else {
            }
            p.checkNextToken("radius");
            p.getNextFloat();
        } else if (type.equals("file-mesh")) {
            System.out.println("Reading file mesh: " + name + " ... ");
            p.checkNextToken("filename");
            p.getNextToken();
            if (p.peekNextToken("smooth_normals")) {
            	p.getNextBoolean();
            }
        } else if (type.equals("bezier-mesh")) {
            System.out.println("Reading bezier mesh: " + name + " ... ");
            p.checkNextToken("n");
            int nu = p.getNextInt();
            int nv = p.getNextInt();
            if (p.peekNextToken("wrap")) {
            	p.getNextBoolean();
            	p.getNextBoolean();
            }
            p.checkNextToken("points");
            float[] points = new float[3 * nu * nv];
            for (int i = 0; i < points.length; i++)
                points[i] = p.getNextFloat();
            if (p.peekNextToken("subdivs"))
            	p.getNextInt();
            if (p.peekNextToken("smooth")) {
            	p.getNextBoolean();
            }
        } else {
            System.out.println("Unrecognized object type: " + p.getNextToken());
            noInstance = true;
        }
        if (!noInstance) {
            if (modifiers != null)
            if (transform != null) {
            }
        }
        p.checkNextToken("}");
        
        
    }

    private ArrayList<Shader> getShaderList(ArrayList<String> shaderNameList, String name) {
    	ArrayList<Shader> shaderlist = new ArrayList<Shader>();
    	Shader currentShader = null;
    	for (String shaderName : shaderNameList) {
    		currentShader = shaders.get(shaderName);
    		if(currentShader == null){
    			System.out.println("Shader "+ shaderName+" type not implemented. Object "+name+" ignored");
    				return null;
    		}
    		shaderlist.add(currentShader);
		}
    	
		return shaderlist;
	}

	/*private void setPointNormals(Vector3[] points, float[] normalCoords) {
    	int j = 0;
		for (int i = 0; i < normalCoords.length; i+=3) {
			points[j++].setNormal(new Vector3(normalCoords[i], normalCoords[i+1], normalCoords[i+2]));
		}
	}

	private Triangle[] getMeshTriangles(int nt, Point3[] points,
			int[] triangleIndices) {
    	Triangle[] triangles = new Triangle[nt];
    	int j = 0;
		for (int i = 0; i < triangleIndices.length; i+=3) {
			triangles[j++] = new Triangle(points[triangleIndices[i]], points[triangleIndices[i+1]], points[triangleIndices[i+2]]);
		}
    	
		return triangles;
	}*/
    

	

	private Point3[] getMeshPoints(int np, float[] pointCoords) {
    	Point3[] points = new Point3[np];
    	int j = 0;
    	for (int i = 0; i < pointCoords.length; i+=3) {
			points[j++] = new Point3(pointCoords[i], pointCoords[i+1], pointCoords[i+2]); 
		}
		return points;
	}

	private void parseInstanceBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("name");
        String name = p.getNextToken();
        System.out.println("Reading instance: " + name + " ...");
        p.checkNextToken("geometry");
        p.getNextToken();
        p.checkNextToken("transform");
        parseMatrix();
        String[] shaders;
        if (p.peekNextToken("shaders")) {
            int n = p.getNextInt();
            shaders = new String[n];
            for (int i = 0; i < n; i++)
                shaders[i] = p.getNextToken();
        } else {
            p.checkNextToken("shader");
            shaders = new String[] { p.getNextToken() };
        }
        String[] modifiers = null;
        if (p.peekNextToken("modifiers")) {
            int n = p.getNextInt();
            modifiers = new String[n];
            for (int i = 0; i < n; i++)
                modifiers[i] = p.getNextToken();
        } else if (p.peekNextToken("modifier"))
            modifiers = new String[] { p.getNextToken() };
        if (modifiers != null) {
        }
        p.checkNextToken("}");
    }

    private void parseLightBlock(Scene renderscene) throws ParserException, IOException {
        p.checkNextToken("{");
        p.checkNextToken("type");
        if (p.peekNextToken("mesh")) {
            System.out.println("Deprecated light type: mesh");
            p.checkNextToken("name");
            String name = p.getNextToken();
            System.out.println("Reading light mesh: " + name + " ...");
            p.checkNextToken("emit");
            parseColor();
            int samples = numLightSamples;
            if (p.peekNextToken("samples"))
                samples = p.getNextInt();
            else
                System.out.println("Samples keyword not found - defaulting to " + samples);
            int numVertices = p.getNextInt();
            int numTriangles = p.getNextInt();
            float[] points = new float[3 * numVertices];
            int[] triangles = new int[3 * numTriangles];
            for (int i = 0; i < numVertices; i++) {
                p.checkNextToken("v");
                points[3 * i + 0] = p.getNextFloat();
                points[3 * i + 1] = p.getNextFloat();
                points[3 * i + 2] = p.getNextFloat();
                p.getNextFloat();
                p.getNextFloat();
                p.getNextFloat();
                p.getNextFloat();
                p.getNextFloat();
            }
            for (int i = 0; i < numTriangles; i++) {
                p.checkNextToken("t");
                triangles[3 * i + 0] = p.getNextInt();
                triangles[3 * i + 1] = p.getNextInt();
                triangles[3 * i + 2] = p.getNextInt();
            }
        } else if (p.peekNextToken("point")) {
            System.out.println("Reading point light ...");
            Color pow;
            float po = 100;
            if (p.peekNextToken("color")) {
                pow = parseColor();
                p.checkNextToken("power");
                po = p.getNextFloat();                
            } else {
                System.out.println("Deprecated color specification - please use color and power instead");
                p.checkNextToken("power");
                pow = parseColor();
            }
            p.checkNextToken("p");
			PointLight light = new PointLight(parsePoint());
			light.setColor(pow);
			light.setPower(po);
			renderscene.addLight( light);
		
            
            
        } else if (p.peekNextToken("spherical")) {
            System.out.println("Reading spherical light ...");
            p.checkNextToken("color");
            parseColor();
            p.checkNextToken("radiance");
            p.getNextFloat();
            p.checkNextToken("center");
            parsePoint();
            p.checkNextToken("radius");
            p.getNextFloat();
            p.checkNextToken("samples");
            p.getNextInt();
        } else if (p.peekNextToken("directional")) {
            System.out.println("Reading directional light ...");
            p.checkNextToken("source");
            parsePoint();
            p.checkNextToken("target");
            parsePoint();
            p.checkNextToken("radius");
            p.getNextFloat();
            p.checkNextToken("emit");
            Color e = parseColor();
            if (p.peekNextToken("intensity")) {
                float i = p.getNextFloat();
                e.mul(i);
            } else
                System.out.println("Deprecated color specification - please use emit and intensity instead");
        } else if (p.peekNextToken("ibl")) {
            System.out.println("Reading image based light ...");
            p.checkNextToken("image");
            p.getNextToken();
            p.checkNextToken("center");
            parseVector();
            p.checkNextToken("up");
            parseVector();
            p.checkNextToken("lock");
            p.getNextBoolean();
            int samples = numLightSamples;
            if (p.peekNextToken("samples"))
                samples = p.getNextInt();
            else
                System.out.println("Samples keyword not found - defaulting to " + samples);
        } else if (p.peekNextToken("meshlight")) {
            p.checkNextToken("name");
            String name = p.getNextToken();
            System.out.println("Reading meshlight: " + name + " ...");
            p.checkNextToken("emit");
            Color e = parseColor();
            if (p.peekNextToken("radiance")) {
                float r = p.getNextFloat();
                e.mul(r);
            } else
                System.out.println("Deprecated color specification - please use emit and radiance instead");
            int samples = numLightSamples;
            if (p.peekNextToken("samples"))
                samples = p.getNextInt();
            else
                System.out.println("Samples keyword not found - defaulting to " + samples);
            p.checkNextToken("points");
            int np = p.getNextInt();
            parseFloatArray(np * 3);
            p.checkNextToken("triangles");
            int nt = p.getNextInt();
            parseIntArray(nt * 3);
        } else if (p.peekNextToken("sunsky")) {
            p.checkNextToken("up");
            parseVector();
            p.checkNextToken("east");
            parseVector();
            p.checkNextToken("sundir");
            parseVector();
            p.checkNextToken("turbidity");
            p.getNextFloat();
            if (p.peekNextToken("samples")) {
            	p.getNextInt();
            }
        } else
            System.out.println("Unrecognized object type: " + p.getNextToken());
        p.checkNextToken("}");
    }

    private Color parseColor() throws IOException, ParserException {
        if (p.peekNextToken("{")) {
            String space = p.getNextToken();
            Color c = null;
            if (space.equals("sRGB nonlinear")) {
                float r = p.getNextFloat();
                float g = p.getNextFloat();
                float b = p.getNextFloat();
                c = new Color(r, g, b);
                c.toLinear();
            } else if (space.equals("sRGB linear")) {
                float r = p.getNextFloat();
                float g = p.getNextFloat();
                float b = p.getNextFloat();
                c = new Color(r, g, b);
            } else
                System.out.println("Unrecognized color space: " + space);
            p.checkNextToken("}");
            return c;
        } else {
            float r = p.getNextFloat();
            float g = p.getNextFloat();
            float b = p.getNextFloat();
            return new Color(r, g, b);
        }
    }

    private Point3 parsePoint() throws IOException {
        float x = p.getNextFloat();
        float y = p.getNextFloat();
        float z = p.getNextFloat();
        return new Point3(x, y, z);
    }

    private Vector3 parseVector() throws IOException {
        float x = p.getNextFloat();
        float y = p.getNextFloat();
        float z = p.getNextFloat();
        return new Vector3(x, y, z);
    }

    private int[] parseIntArray(int size) throws IOException {
        int[] data = new int[size];
        for (int i = 0; i < size; i++)
            data[i] = p.getNextInt();
        return data;
    }

    private float[] parseFloatArray(int size) throws IOException {
        float[] data = new float[size];
        for (int i = 0; i < size; i++)
            data[i] = p.getNextFloat();
        return data;
    }

    private Matrix4 parseMatrix() throws IOException, ParserException {
        if (p.peekNextToken("row")) {
            return new Matrix4(parseFloatArray(16), true);
        } else if (p.peekNextToken("col")) {
            return new Matrix4(parseFloatArray(16), false);
        } else {
            Matrix4 m = Matrix4.IDENTITY;
            p.checkNextToken("{");
            while (!p.peekNextToken("}")) {
                Matrix4 t = null;
                if (p.peekNextToken("translate")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    t = Matrix4.translation(x, y, z);
                } else if (p.peekNextToken("scaleu")) {
                    float s = p.getNextFloat();
                    t = Matrix4.scale(s);
                } else if (p.peekNextToken("scale")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    t = Matrix4.scale(x, y, z);
                } else if (p.peekNextToken("rotatex")) {
                    float angle = p.getNextFloat();
                    t = Matrix4.rotateX((float) Math.toRadians(angle));
                } else if (p.peekNextToken("rotatey")) {
                    float angle = p.getNextFloat();
                    t = Matrix4.rotateY((float) Math.toRadians(angle));
                } else if (p.peekNextToken("rotatez")) {
                    float angle = p.getNextFloat();
                    t = Matrix4.rotateZ((float) Math.toRadians(angle));
                } else if (p.peekNextToken("rotate")) {
                    float x = p.getNextFloat();
                    float y = p.getNextFloat();
                    float z = p.getNextFloat();
                    float angle = p.getNextFloat();
                    t = Matrix4.rotate(x, y, z, (float) Math.toRadians(angle));
                } else
                    System.out.println("Unrecognized transformation type: " + p.getNextToken());
                if (t != null)
                    m = t.multiply(m);
            }
            return m;
        }
    }
}

